-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
efi: change how HostEnvironment overrides EFI variables #312
efi: change how HostEnvironment overrides EFI variables #312
Conversation
The HostEnvironment interface contains a ReadVar method for abstracting reads of EFI variables. The default environment just calls efi.ReadVariable, with other implementations providing mocked variables. The github.com/canonical/go-efilib package provides a lot more APIs now that read from (and some that write to) EFI variables and some of these will be used from the new efi/preinstall package (these APIs are mostly boot and secure boot related). I wanted a way to be able to override the environment for the preinstall.RunChecks call (particularly as this is very useful for testing), but the existing HostEnvironment approach doesn't work well for this. I initially considered a couple of options: - Make it possible to mock each of the individual calls to go-efilib functions via the HostEnvironment interface. - Export varsBackend from go-efilib and provide a function in to mock the backend by overriding the system one. The first approach scales poorly, so my initial approach was going to be the second one. The problem with this though is that HostEnvironment will be part of a public, production API, and mocking the backend is global to the whole process. I don't think this is appropriate in production code where it might be possible that there may be one goroutine reading EFI variables from the system when another goroutine decides to mock the backend based on a supplied HostEnvironment. I've taken a different approach instead, by modifying every go-efilib function that interacts with EFI variables to accept a context.Context argument. The context contains a VarsBackend keyed from it, and the package exports a DefaultVarContext symbol corresponding to the default system backend (efivarfs if it is detected, or a null backend if it isn't detected). This approach permits individual call sites to override the backend to use for individual functions that interact with EFI variables (not just ReadVariable, WriteVariable and ListVariables, but there's a whole set of extra functions related to secure boot and boot configuration that make use of reading and writing EFI variables). The ReadVar method on the HostEnvironment interface has gone, and a new method that returns a context has been added. The internal.DefaultEnv implementation just returns efi.DefaultVarContext. The MockVars type in internal/efitest has been updated to implement efi.VarsBackend, and the MockHostEnvironment implementation in internal/efitest returns a context that keys to this implementation. This should hopefully allow preinstall.RunChecks to be able to take a HostEnvironment argument and benefit from mocked variables even when interacting with variables indirectly using higher level functions exported from go-efilib.
9474e8f
to
aaba2c5
Compare
aaba2c5
to
12ec80f
Compare
efi/env.go
Outdated
// VarContext returns a context containing a VarsBackend, keyed by efi.VarsBackendKey, | ||
// for interacting with EFI variables via go-efilib. This context can be passed to any | ||
// go-efilib function that interacts with EFI variables. | ||
VarContext() context.Context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a bit a strange API for contexts, if a caller gets a context it usually should pass it along, and not start with a fresh one, so a more expected API would be:
VartContext(parent context.Context) context.Context
which would return something derived from parent (and panic if parent is nil) instead of returning a fresh context in all cases
otherwise in some scenarios you would end up having to pass more than 1 context.Context to a function which is not how context is usually meant to be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Funnily enough, I'd already added an API for this, so I've just pulled in the go-efilib version with it and used it in the default implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure the new APIs fully match expectations about context usage
efi/internal/default_env.go
Outdated
return readVar(name, guid) | ||
// VarContext implements [HostEnvironment.VarContext]. | ||
func (e defaultEnvImpl) VarContext() context.Context { | ||
return efi.DefaultVarContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so this really should really be some form of WithDefaultVarContext from efi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I've updated this now.
12ec80f
to
35d03b3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the change, couple small remarks
// backend will support this eventually for variable writes because it currently implements | ||
// a retry loop that has a potential to race with other processes. Cancelation and / or | ||
// deadlines for sections of code that performs multiple reads using efivarfs may be useful | ||
// in the future because the kernel rate-limits reads per-user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should the doc say something about parent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the documentation now.
ctx := DefaultEnv.VarContext(context.Background()) | ||
c.Assert(ctx, NotNil) | ||
|
||
expected := efi.WithDefaultVarsBackend(context.Background()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe a good idea to pass something with an attached value already and check it can be retrieved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added this to the test
// backend will support this eventually for variable writes because it currently implements | ||
// a retry loop that has a potential to race with other processes. Cancelation and / or | ||
// deadlines for sections of code that performs multiple reads using efivarfs may be useful | ||
// in the future because the kernel rate-limits reads per-user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same about parent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you
The
HostEnvironment
interface contains aReadVar
method for abstractingreads of EFI variables. The default environment just calls
efi.ReadVariable
, with other implementations providing mocked variables.The
github.com/canonical/go-efilib
package provides a lot more APIs nowthat read from (and some that write to) EFI variables and some of these
will be used from the new
efi/preinstall
package (these APIs are mostlyboot and secure boot related). I wanted a way to be able to override the
environment for the
preinstall.RunChecks
call (particularly as this isvery useful for testing), but the existing
HostEnvironment
approach doesn'twork well for this.
I initially considered a couple of options:
functions via the
HostEnvironment
interface.varsBackend
from go-efilib and provide a function in to mockthe backend by overriding the system one.
The first approach scales poorly, so my initial approach was going to be
the second one. The problem with this though is that
HostEnvironment
will be part of a public, production API, and mocking the backend is global
to the whole process. I don't think this is appropriate in production code
where it might be possible that there may be one goroutine reading EFI
variables from the system when another goroutine decides to mock the backend
based on a supplied
HostEnvironment
.I've taken a different approach instead, by modifying every go-efilib
function that interacts with EFI variables to accept a
context.Context
argument. The context contains a
VarsBackend
keyed from it, and the packageexports a
DefaultVarContext
symbol corresponding to the default systembackend (
efivarfs
if it is detected, or a null backend if it isn'tdetected). I've used a context rather than passing the
VarsBackend
directlybecause the default efivarfs backend may make use of the optional
cancellation and deadline during writing, as it runs a retry loop that potentially
races with other applications.
This approach permits individual call sites to override the backend to
use for individual functions that interact with EFI variables (not just
ReadVariable
,WriteVariable
andListVariables
, but there's a whole setof extra functions related to secure boot and boot configuration that
make use of reading and writing EFI variables).
The
ReadVar
method on theHostEnvironment
interface has gone, and a newmethod that returns a context has been added. The
internal.DefaultEnv
implementation just returns
efi.DefaultVarContext
.The
MockVars
type ininternal/efitest
has been updated to implementefi.VarsBackend
, and theMockHostEnvironment
implementation ininternal/efitest
returns a context that keys to this implementation.This should hopefully allow
preinstall.RunChecks
to be able to take aHostEnvironment
argument and benefit from mocked variables even wheninteracting with variables indirectly using higher level functions
exported from go-efilib.